package com.leectrl.adapter;


import android.os.Build;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.RequiresApi;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.hdl.school.bean.User;
import com.leectrl.bean.HttpLabel;
import com.leectrl.controller.WebController;
import com.leectrl.utils.*;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelProgressiveFuture;
import io.netty.channel.ChannelProgressiveFutureListener;
import io.netty.channel.DefaultFileRegion;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedFile;
import io.netty.util.CharsetUtil;

import javax.activation.MimetypesFileTypeMap;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.lang.annotation.Annotation;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.*;

@ChannelHandler.Sharable
public class HttpChannelHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    private FullHttpRequest request = null;
    private FullHttpResponse response = null;
    public static final String HTTP_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
    public static final String HTTP_DATE_GMT_TIMEZONE = "GMT";
    public static final int HTTP_CACHE_SECONDS = 0;
    LocalCacheManage localCacheManage=LocalCacheManage.getInstance();

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
        String uri = request.getUri();
        if (uri.contains("?")) {
            uri = uri.substring(0, uri.indexOf("?"));
        }
        Object resp = null;
        HttpRouter httpRouter = HttpRouter.getInstance();
        Action action = httpRouter.getRoute(new HttpLabel(uri, request.method()));

        if (action != null) {
            User user=null;
            if(action.isCheckSession()){
                HttpHeaders httpHeaders=request.headers();
                if(httpHeaders.contains("user_id")&&httpHeaders.contains("session")){
                    int userId=httpHeaders.getInt("user_id");
                    String session=httpHeaders.get("session");
                    String sessionInfoStr=localCacheManage.get("session_"+userId);
                    if(TextUtils.isEmpty(sessionInfoStr)){
                        response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(ResponseUtil.responseFailureAuthMsg().getBytes("UTF-8")));
                        ctx.write(response);
                        ctx.flush();//刷新后才将数据发出到SocketChannel
                        ctx.close();
                        return ;
                    }
                    JSONObject sessionObj=JSON.parseObject(sessionInfoStr);
                    if(!session.equals(sessionObj.getString("session"))){
                        response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(ResponseUtil.responseFailureAuthMsg().getBytes("UTF-8")));
                        ctx.write(response);
                        ctx.flush();//刷新后才将数据发出到SocketChannel
                        ctx.close();
                        return ;
                    }
                    user=new User();
                    user.setId(userId);
                    user.setName(sessionObj.getString("uname"));
                    user.setLevel(sessionObj.getIntValue("level"));
                }else{
                    response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(ResponseUtil.responseFailureAuthMsg().getBytes("UTF-8")));
                    ctx.write(response);
                    ctx.flush();//刷新后才将数据发出到SocketChannel
                    ctx.close();
                    return ;
                }

            }


            String s = request.uri();
            QueryStringDecoder queryStringDecoder = new QueryStringDecoder(s);
            Map<String, List<String>> parameters = queryStringDecoder.parameters();
            Class[] classes = action.getMethod().getParameterTypes();
            Object[] objects = new Object[classes.length];

            for (int i = 0; i < classes.length; i++) {
                Class c = classes[i];
                if(c.getName().equals(FullHttpRequest.class.getName())){
                    objects[i]=request;
                }else if(c.getName().equals(User.class.getName())){
                    objects[i]=user;
                }else{
                    //处理@RequestBody注解
                    Annotation[] parameterAnnotation = action.getMethod().getParameterAnnotations()[i];
                    if (parameterAnnotation.length > 0) {
                        for (int j = 0; j < parameterAnnotation.length; j++) {
                            if (parameterAnnotation[j].annotationType() == RequestBody.class &&
                                    request.headers().get(HttpHeaderNames.CONTENT_TYPE.toString()).equals(HttpHeaderValues.APPLICATION_JSON.toString())) {
                                if (JSONObject.class.getName().equals(c.getName())) {
                                    objects[i] = JsonUtil.fromJson(request);
                                } else {
                                    objects[i] = JsonUtil.fromJson(request, c);
                                }
                            } else if (parameterAnnotation[j].annotationType() == RequestParams.class) {
                                RequestParams requestParams = (RequestParams) parameterAnnotation[j];
                                if (Integer.class.getName().equals(c.getName())) {
                                    objects[i] = Integer.parseInt(parameters.get(requestParams.name()).get(0));
                                } else if (String.class.getName().equals(c.getName())) {
                                    objects[i] = parameters.get(requestParams.name()).get(0);
                                }
                            }
                        }
                    }
                }

            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                resp = action.call(objects);
            }
        } else {
            resp = WebController.request(uri);
        }
        if (resp == null) {
            response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND, Unpooled.wrappedBuffer("Not Found".getBytes("UTF-8")));
            ctx.write(response);
            ctx.flush();//刷新后才将数据发出到SocketChannel
            ctx.close();
        } else {
            if (resp instanceof File) {
                File respFile = (File) resp;
                final boolean keepAlive = HttpUtil.isKeepAlive(request);
                RandomAccessFile raf = null;
                long fileLength = -1;
                try {
                    raf = new RandomAccessFile(respFile, "r");
                    fileLength = raf.length();
                } catch (Exception e) {
                    ctx.writeAndFlush("ERR: " + e.getClass().getSimpleName() + ": " + e.getMessage() + '\n');
                    return;
                } finally {
                    if (fileLength < 0 && raf != null) {
                        raf.close();
                    }
                }

                HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);

                HttpUtil.setContentLength(response, fileLength);
                setContentTypeHeader(response, respFile);
                setDateAndCacheHeaders(response, respFile);

                if (!keepAlive) {
                    response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
                } else if (request.protocolVersion().equals(HttpVersion.HTTP_1_0)) {
                    response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
                }

                // Write the initial line and the header.
                ctx.write(response);

                // Write the content.
                ChannelFuture sendFileFuture;
                ChannelFuture lastContentFuture;
                if (ctx.pipeline().get(SslHandler.class) == null) {
                    sendFileFuture =
                            ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength), ctx.newProgressivePromise());
                    // Write the end marker.
                    lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
                } else {
                    sendFileFuture =
                            ctx.writeAndFlush(new HttpChunkedInput(new ChunkedFile(raf, 0, fileLength, 8192)),
                                    ctx.newProgressivePromise());
                    // HttpChunkedInput will write the end marker (LastHttpContent) for us.
                    lastContentFuture = sendFileFuture;
                }
                final String reqPath = uri;
                sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
                    @Override
                    public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) {
                        if (total < 0) { // total unknown
                            System.err.println(future.channel() + " Transfer progress: " + progress);
                        } else {
                            System.err.println(future.channel() + " Transfer progress: " + progress + " / " + total);
                        }
                    }

                    @Override
                    public void operationComplete(ChannelProgressiveFuture future) {
                        System.err.println(future.channel() + " Transfer complete.");
                    }
                });

                // Decide whether to close the connection or not.
                if (!keepAlive) {
                    // Close the connection when the whole content is written out.
                    lastContentFuture.addListener(ChannelFutureListener.CLOSE);
                }
                if (!keepAlive) {
                    // Close the connection when the whole content is written out.
                    lastContentFuture.addListener(ChannelFutureListener.CLOSE);
                }
            } else if (resp instanceof JSONObject) {
                response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(JSON.toJSONString((JSONObject) resp).getBytes("UTF-8")));
                response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=utf-8");
                ctx.write(response);
                ctx.flush();//刷新后才将数据发出到SocketChannel
                ctx.close();
            }  else if (resp instanceof JSONArray) {
                response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(JSON.toJSONString((JSONArray) resp).getBytes("UTF-8")));
                response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=utf-8");
                ctx.write(response);
                ctx.flush();//刷新后才将数据发出到SocketChannel
                ctx.close();
            } else {

                response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(((String) resp).getBytes("UTF-8")));
                MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();
                mimeTypesMap.addMimeTypes("text/css css\r\napplication/javascript js\r\ntext/html html");
                String url = uri;
                if (uri.contains(".")) {
                    if (uri.equals("") || uri.equals("/")) {
                        url = "/index.html";
                    }
                    response.headers().set(HttpHeaderNames.CONTENT_TYPE, mimeTypesMap.getContentType(url));
                } else {
                    response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
                }
                ctx.write(response);
                ctx.flush();//刷新后才将数据发出到SocketChannel
                ctx.close();
            }

        }

    }

    private static void setContentTypeHeader(HttpResponse response, File file) {
        MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();
        mimeTypesMap.addMimeTypes("text/css css\r\napplication/javascript js\r\ntext/html html");
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, mimeTypesMap.getContentType(file.getPath()));
    }

    /**
     * If Keep-Alive is disabled, attaches "Connection: close" header to the response
     * and closes the connection after the response being sent.
     */
    private void sendAndCleanupConnection(ChannelHandlerContext ctx, FullHttpResponse response) {
        final FullHttpRequest request = this.request;
        final boolean keepAlive = HttpUtil.isKeepAlive(request);
        HttpUtil.setContentLength(response, response.content().readableBytes());
        if (!keepAlive) {
            // We're going to close the connection as soon as the response is sent,
            // so we should also make it clear for the client.
            response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
        } else if (request.protocolVersion().equals(HttpVersion.HTTP_1_0)) {
            response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        }

        ChannelFuture flushPromise = ctx.writeAndFlush(response);

        if (!keepAlive) {
            // Close the connection as soon as the response is sent.
            flushPromise.addListener(ChannelFutureListener.CLOSE);
        }
    }

    /**
     * Sets the Date header for the HTTP response
     *
     * @param response HTTP response
     */
    private static void setDateHeader(FullHttpResponse response) {
        SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
        dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE));

        Calendar time = new GregorianCalendar();
        response.headers().set(HttpHeaderNames.DATE, dateFormatter.format(time.getTime()));
    }

    private static void setDateAndCacheHeaders(HttpResponse response, File fileToCache) {
        SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
        dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE));

        // Date header
        Calendar time = new GregorianCalendar();
        response.headers().set(HttpHeaderNames.DATE, dateFormatter.format(time.getTime()));

        // Add cache headers
        time.add(Calendar.SECOND, HTTP_CACHE_SECONDS);
        response.headers().set(HttpHeaderNames.EXPIRES, dateFormatter.format(time.getTime()));
        response.headers().set(HttpHeaderNames.CACHE_CONTROL, "private, max-age=" + HTTP_CACHE_SECONDS);
        response.headers().set(
                HttpHeaderNames.LAST_MODIFIED, dateFormatter.format(new Date(fileToCache.lastModified())));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        System.out.println("server exceptionCaught..");
        ctx.close();
    }
}
